use proc_macro::TokenStream;
use quote::ToTokens;
use syn::visit_mut::VisitMut;

#[proc_macro_attribute]
pub fn sorted(_args: TokenStream, input: TokenStream) -> TokenStream {
    let temp = input.clone();
    let st = syn::parse_macro_input!(input as syn::Item);
    match impl_sorted(&st) {
        Ok(_) => temp,
        Err(e) => {
            eprintln!("{:#?}", e);
            e.into_compile_error().into()
        },
    }
}

fn impl_sorted(st: &syn::Item) -> syn::Result<()> {
    eprintln!("{:#?}", st);
    match st {
        syn::Item::Enum(item_enum) => {
            check_sorted(item_enum)
        },
        _ => {
            Err(syn::Error::new(proc_macro2::Span::call_site(), "expected enum or match expression"))
        }
    }
}


fn check_sorted(item_enum: &syn::ItemEnum) -> syn::Result<()> {
    let origin_variants = item_enum.variants
        .iter()
        .collect::<Vec<_>>();
    let mut sorted_variants = origin_variants.clone();
    sorted_variants.sort_by(|a, b| a.ident.to_string().cmp(&b.ident.to_string()));
    for (source,sorted) in origin_variants.iter().zip(sorted_variants.iter()) {
        if source != sorted {
            return Err(syn::Error::new(sorted.ident.span(), format!("{} should sort before {}",sorted.ident, source.ident)));
        }
    }
    Ok(())
}



#[proc_macro_attribute]
pub fn check(_args: TokenStream, input: TokenStream) -> TokenStream {
    let mut st = syn::parse_macro_input!(input as syn::ItemFn);
    match impl_checked(&mut st) {
        Ok(token_stream) => token_stream.into(),
        Err(e) => {
            // eprintln!("{:#?}", e);
            // e.into_compile_error().into()
            // 下面这一行拿到的TokenStream是空的，里面只包含了错误信息，没有代码信息
            let mut t = e.to_compile_error();
            // 将原始的用户代码塞进去，这样返回结果中既包含代码信息，也包含错误信息
            t.extend(st.to_token_stream());
            t.into()
        },
    }
}

fn impl_checked(st: &mut syn::ItemFn) -> syn::Result<proc_macro2::TokenStream> {
    eprintln!("{:#?}", st);
    let mut visitor = MatchVisitor {
        error: None,
    };
    visitor.visit_item_fn_mut(st);
    if visitor.error.is_some() {
        Err(visitor.error.unwrap())
    }else {
        Ok(st.to_token_stream())
    }

}

struct MatchVisitor {
    error: Option<syn::Error>,
}


fn join_path_name(path: &syn::Path) -> String {
    path.segments
        .iter()
        .map(|segment| segment.ident.to_string())
        .collect::<Vec<_>>()
        .join("::")
}

impl syn::visit_mut::VisitMut for MatchVisitor {

    fn visit_expr_match_mut(&mut self, node: &mut syn::ExprMatch) {
        eprintln!("修改前的节点属性{:#?}", node.attrs);
        node.attrs.retain(|attr| {
            if let syn::Meta::Path(path) = &attr.meta {
                let segment_path = join_path_name(path);
                eprintln!("segment path: {}", segment_path);
                return segment_path != "sorted";
            }
            false
        });
        eprintln!("修改后的节点属性{:#?}", node.attrs);


        let mut my_arms:Vec<(String, &dyn ToTokens)> = Vec::new();
        for arm in &node.arms {
            eprintln!("Arm:{:#?}", arm);
            match &arm.pat {
                syn::Pat::Struct(struct_pat) => {
                    my_arms.push((join_path_name(&struct_pat.path), &struct_pat.path));
                },
                syn::Pat::Path(path) => {
                    my_arms.push((join_path_name(&path.path),&path.path));
                },
                syn::Pat::TupleStruct(tuple_struct) => {
                    my_arms.push((join_path_name(&tuple_struct.path), &tuple_struct.path));
                },
                syn::Pat::Ident(ident) => {
                    my_arms.push((ident.ident.to_string(), &ident.ident));
                },
                syn::Pat::Wild(wild) => {
                    my_arms.push(("_".to_string(), &wild.underscore_token));
                }
                _ => {
                    self.error = Some(syn::Error::new_spanned(&arm.pat, "unsupported by #[sorted]"));
                    return;
                }

            }
        }

        let mut sorted_arms:Vec<(String, &dyn ToTokens)> = my_arms.clone();
        sorted_arms.sort_by(|a, b| a.0.cmp(&b.0));
        eprintln!("排序前的：{:#?}", my_arms.iter().map(|x| &x.0).collect::<Vec<_>>());
        eprintln!("排序后的：{:#?}", sorted_arms.iter().map(|x| &x.0).collect::<Vec<_>>());


        for (source, sorted) in my_arms.iter().zip(sorted_arms.iter()) {
            if source.0 != sorted.0{
                eprintln!("source:{:#?},sorted:{:#?}", source.0,sorted.0);
                self.error = Some(syn::Error::new_spanned(&sorted.1, format!("{} should sort before {}",sorted.0, source.0)));
                break;
            }
        }
        // syn::visit_mut::visit_expr_match_mut(self, node)

    }

}
